package com.linkoog.devtools.dubbo.toolwindow.workbenche.right;

import com.fasterxml.jackson.databind.JsonNode;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.IconLoader;
import com.intellij.ui.components.JBLoadingPanel;
import com.intellij.ui.components.JBTabbedPane;
import com.intellij.ui.tabs.TabInfo;
import com.linkoog.devtools.dubbo.invoke.DubboInvoker;
import com.linkoog.devtools.dubbo.invoke.telnet.TelnetDubboInvoker;
import com.linkoog.devtools.dubbo.raw_index.DubboMethod;
import com.linkoog.devtools.dubbo.invoke.DubboRequest;
import com.linkoog.devtools.dubbo.toolwindow.workbenche.left.DubboFavorite;
import com.linkoog.devtools.dubbo.toolwindow.workbenche.left.DubboHistory;
import com.linkoog.devtools.type.Type;
import com.linkoog.devtools.utils.Assert;
import com.linkoog.devtools.utils.JacksonUtil;
import com.linkoog.devtools.utils.StringUtils;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class DubboTestRightPanel implements Disposable {
    private static NotificationGroup notificationGroup;

    static {
        notificationGroup = NotificationGroup.findRegisteredGroup("com.github.linkoog.DevTools.Dubbo");
    }

    private Project project;
    private JPanel myMainPanel;
    private JTextField addressTextField;
    private JTextField interfaceTextField;
    private JTextField methodTextField;
    private JTextField versionTextField;
    private JTextField groupTextField;

    private JPanel contextPanel;
    private JPanel leftContextPanel;
    private JTabbedPane paramTabs;

    private JPanel rightContextPanel;
    private JTabbedPane respTabs;

    private JButton sendButton;
    private JButton saveAsButton;
    private JLabel envSwitch;
    private final ReentrantLock sendLock = new ReentrantLock();

    private ParamWidget paramWidget;
    private ParamImplicitWidget paramImplicitWidget;
    private RespBodyWidget responseBodyWidget;

    private final JBLoadingPanel loadingPanel;

    private int requestNum = 1;
    private Consumer<DubboHistory> sendClickCallback;
    private Consumer<DubboFavorite> saveClickCallback;



    public DubboTestRightPanel(Project project, JBLoadingPanel loadingPanel) {
        this.project = project;
        this.loadingPanel = loadingPanel;

        initSendButton();
        initSaveButton();
        initEnvSwitch();
        initContainerPanel();
    }

    public void setSendClickCallback(Consumer<DubboHistory> sendClickCallback) {
        this.sendClickCallback = sendClickCallback;
    }

    public void setSaveClickCallback(Consumer<DubboFavorite> saveClickCallback) {
        this.saveClickCallback = saveClickCallback;
    }

    private void initSendButton(){
        //不允许狂点
        //sendButton.setMultiClickThreshhold(200);
        sendButton.setIcon(IconLoader.getIcon("/icons/dubbo/send.svg", DubboTestRightPanel.class));
        sendButton.addActionListener(actionEvent -> {
            // 发送 Dubbo 请求
            try {
                Assert.isTrue( !sendLock.isLocked(), "已经有Dubbo请求，请稍后重试<br/>");

                //address
                String address = addressTextField.getText().trim();
                Assert.isNotBlank(address, "请设置address请求地址<br/>");
                Assert.isTrue(address.startsWith("dubbo://") || address.startsWith("zookeeper://") || address.startsWith("nacos://"), "请设置正确的求地址: Dubbo/Zookeeper/Nacos <br/>");
                Assert.isTrue(address.startsWith("dubbo://"), "目前只支持dubbo协议，暂不支持zookeeper和nacos注册中心方式 <br/>");

                //inface
                String inface = interfaceTextField.getText().trim();
                Assert.isNotBlank(inface, "请设置inface接口名<br/>");

                //method
                String methodName = methodTextField.getText().trim();
                Assert.isNotBlank(methodName, "请设置method方法名<br/>");

                //处理ip和port
                String protocol = "dubbo";
                String ip = "127.0.0.1";
                int port = 20880;
                if (address.startsWith("dubbo://")){
                    String temp = address.substring("dubbo://".length());

                    int index = temp.lastIndexOf(":");
                    if (index != -1 && index != temp.length() -1){
                        ip = temp.substring(0, index);
                        String portStr = temp.substring(index + 1);
                        port = Assert.callable("错误的端口", () -> Integer.parseInt(portStr));
                    }
                }
                Assert.isNotBlank(ip, "请设置ip地址<br/>");
                Assert.isTrue(port > 0 && port < 65536, "错误的端口号<br/>");



                Map<String, String> map = paramImplicitWidget.getParamTable().getKeyValue();
                DubboRequest dubboRequest = new DubboRequest();
                dubboRequest.setProtocol(protocol);
                dubboRequest.setIp(ip);
                dubboRequest.setPort(port);
                dubboRequest.setInface(inface);
                dubboRequest.setMethodName(methodName);
                dubboRequest.setVersion(versionTextField.getText());
                dubboRequest.setGroup(groupTextField.getText());
                dubboRequest.setParamImplicits(map);

                String paramBody = paramWidget.getParamBody();
                Assert.isNotBlank(paramBody, "参数Body不能为空 <br/>");

                JsonNode jsonNode = Assert.callable( "参数Body解析失败，请检查参数字符串。", () -> JacksonUtil.readTree(paramBody));
                Assert.isTrue(jsonNode != null && jsonNode.isObject(),  "参数Body格式错误，应该为JSON格式。");

                //参数Type类型
                JsonNode paramType = jsonNode.get("paramType");
                int typeNum = 0;
                if (paramType == null){
                    dubboRequest.setParamType(new String[] {});
                } else {
                    Assert.isTrue(paramType.isArray(), "参数paramType类型错误，应该为字符串数组");
                    Iterator<JsonNode> it = paramType.iterator();
                    String[] strings = new String[paramType.size()];
                    typeNum = paramType.size();
                    int i = 0;
                    while (it.hasNext()) {
                        JsonNode node = it.next();
                        Assert.isTrue(node != null && node.isTextual(), "paramType的元素类型错误，应该为字符串类型");
                        strings[i] = Assert.callable("paramType的元素，获取数据失败，请检查参数字符串", node::textValue);
                        i++;
                    }

                    dubboRequest.setParamType(strings);
                }

                //参数值
                JsonNode param = jsonNode.get("param");
                if (param == null){
                    dubboRequest.setParamObj(new Object[] {});
                } else {
                    Assert.isTrue(param.isArray(), "参数param类型错误，应该为数组类型");
                    Assert.isTrue(param.size() == typeNum, "参数param数量错误，应该是与参数value数量一致的");

                    Iterator<JsonNode> it = param.iterator();
                    Object[] objects = new Object[param.size()];
                    int i = 0;
                    while (it.hasNext()) {
                        JsonNode node = it.next();
                        objects[i] = node;
                        i++;
                    }

                    dubboRequest.setParamObj(objects);
                }


                DubboHistory dubboHistory = new DubboHistory();
                dubboHistory.setProtocol(protocol);
                dubboHistory.setIp(ip);
                dubboHistory.setPort(port);
                dubboHistory.setInface(inface);
                dubboHistory.setMethodName(methodName);
                dubboHistory.setVersion(versionTextField.getText());
                dubboHistory.setGroup(groupTextField.getText());
                dubboHistory.setParamImplicits(map);
                dubboHistory.setParamBody(paramBody);
                dubboHistory.setSerialNum((requestNum++));
                dubboHistory.setSuccess(false);

                ApplicationManager.getApplication().executeOnPooledThread(() -> ProgressManager.getInstance().run(new Task.Backgroundable(project, "Sending Dubbo Request") {
                    @Override
                    public void run(@NotNull ProgressIndicator indicator) {

                        dubboHistory.setStartTime(new Date());
                        //DubboInvoker dubboInvoker = new SocketDubboInvoker();
                        DubboInvoker dubboInvoker = new TelnetDubboInvoker();
                        boolean lock = false;
                        try {
                            //lock
                            lock = Assert.callable("Dubbo Test is running, Please try again later!!!", () -> sendLock.tryLock(1, TimeUnit.SECONDS));
                            Assert.isTrue(lock, "Dubbo Test is running, Please try again later!!!");

                            //load
                            if (loadingPanel != null){
                                loadingPanel.setLoadingText("发送Dubbo请求");
                                loadingPanel.startLoading();
                            }

                            StringBuilder builder = new StringBuilder("Run Dubbo Request, ");
                            builder.append("host: ").append(dubboRequest.getIp()).append(":").append(dubboRequest.getPort()).append(",  ");
                            indicator.setText(builder.toString());

                            //request
                            long startTime = System.currentTimeMillis();
                            final String response = dubboInvoker.invoke(dubboRequest);
                            long endTime = System.currentTimeMillis();
                            long costTime = endTime - startTime;

                            //记录
                            dubboHistory.setSuccess(true);
                            dubboHistory.setEndTime(new Date(endTime));
                            dubboHistory.setCostTime(costTime);
                            dubboHistory.setResult(response);
                            SwingUtilities.invokeLater(() -> {
                                responseBodyWidget.showText(response);
                            });
                        }catch (Exception e) {
                            SwingUtilities.invokeLater(() -> {
                                Notification error = notificationGroup.createNotification("Dubbo请求失败:<br/>" + e.getMessage(), NotificationType.INFORMATION);
                                Notifications.Bus.notifyAndHide(error, project);
                                responseBodyWidget.showError("Dubbo请求失败:<br/>" + e.getMessage());
                            });

                            dubboHistory.setSuccess(false);
                            long endTime = System.currentTimeMillis();
                            long costTime = endTime - dubboHistory.getStartTime().getTime();
                            dubboHistory.setEndTime(new Date(endTime));
                            dubboHistory.setCostTime(costTime);
                        }finally {
                            if (lock){
                                sendLock.unlock();
                            }

                            if (loadingPanel != null && loadingPanel.isLoading()){
                                loadingPanel.stopLoading();
                            }

                            invokeDubboRequestCallback(dubboHistory);
                        }
                    }
                }));

            }catch(Exception e){
                SwingUtilities.invokeLater(() -> {
                    Notification error = notificationGroup.createNotification("Dubbo请求失败:<br/>" + e.getMessage(), NotificationType.INFORMATION);
                    Notifications.Bus.notifyAndHide(error, project);
                    responseBodyWidget.showError("Dubbo请求失败:<br/>" + e.getMessage());
                });
            }
        });

    }

    private void invokeDubboRequestCallback(DubboHistory dubboHistory){
        ApplicationManager.getApplication().invokeLater(() -> {
            if (sendClickCallback != null){
                sendClickCallback.accept(dubboHistory);
            }
        });
    }

    private void initSaveButton() {
        //不允许狂点
        //saveAsButton.setMultiClickThreshhold(200);
        saveAsButton.setIcon(IconLoader.getIcon("/icons/dubbo/save.svg", DubboTestRightPanel.class));
        saveAsButton.addActionListener(actionEvent -> {
            SaveDialog dialog = new SaveDialog(project);
            dialog.show();
            if (dialog.isOK()) {
				if (saveClickCallback != null){
                    String protocol = "dubbo";
                    String ip = "127.0.0.1";
                    int port = 20880;
                    String address = addressTextField.getText().trim();
                    if (address.startsWith("dubbo://")){
                        String temp = address.substring("dubbo://".length());

                        int index = temp.lastIndexOf(":");
                        if (index != -1 && index != temp.length() -1){
                            ip = temp.substring(0, index);
                            String portStr = temp.substring(index + 1);
                            port = Assert.callable("错误的端口", () -> Integer.parseInt(portStr));
                        }
                    }

                    Map<String, String> map = paramImplicitWidget.getParamTable().getKeyValue();
                    DubboFavorite dubboFavorite = new DubboFavorite();
                    dubboFavorite.setProtocol(protocol);
                    dubboFavorite.setIp(ip);
                    dubboFavorite.setPort(port);

                    dubboFavorite.setInface(interfaceTextField.getText().trim());
                    dubboFavorite.setMethodName(methodTextField.getText().trim());
                    dubboFavorite.setVersion(versionTextField.getText());
                    dubboFavorite.setGroup(groupTextField.getText());

                    dubboFavorite.setParamBody(paramWidget.getParamBody());
                    dubboFavorite.setParamImplicits(map);

                    String name = dialog.getText();
                    if (StringUtils.isBlank(name)) {
                        name = dubboFavorite.getInface() + "#" + dubboFavorite.getMethodName();
                    }
                    dubboFavorite.setName(name);


                    saveClickCallback.accept(dubboFavorite);
                }
            }
        });
    }

    private void initEnvSwitch(){
        envSwitch.setText("<html><a href='#'>environment</a></html>");
        envSwitch.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if(e.getButton() == MouseEvent.BUTTON1) {
                    // 处理点击事件
                }
            }
        });
    }


    private void initContainerPanel(){
        addressTextField.setText("dubbo://127.0.0.1:8080");

        leftContextPanel = new JPanel(new BorderLayout());
        leftContextPanel.add(paramTabs = new JBTabbedPane(), BorderLayout.CENTER);
        leftContextPanel.setMinimumSize(new Dimension(350, -1));
        leftContextPanel.setBorder(new LineBorder(Color.LIGHT_GRAY));

        rightContextPanel = new JPanel(new BorderLayout());
        rightContextPanel.add(respTabs = new JBTabbedPane(), BorderLayout.CENTER);
        rightContextPanel.setMinimumSize(new Dimension(350, -1));
        rightContextPanel.setBorder(new LineBorder(Color.LIGHT_GRAY));

        // 左右分割面板
        Splitter splitter = new Splitter(false, 0.5F);
        //splitter.setResizeEnabled(false);
        splitter.setDividerPositionStrategy(Splitter.DividerPositionStrategy.KEEP_PROPORTION);
        splitter.setFirstComponent(leftContextPanel);
        splitter.setSecondComponent(rightContextPanel);
        this.contextPanel.add(splitter, BorderLayout.CENTER);

        initParamPanel();
        initResponsePanel();
    }

    private void initParamPanel(){
        paramWidget = new ParamWidget(project);
        Disposer.register(this, paramWidget);
        TabInfo paramBodyInfo = new TabInfo(paramWidget.container);
        paramBodyInfo.setText("参数列表");
        paramTabs.addTab("参数列表", paramBodyInfo.getComponent());

        paramImplicitWidget = new ParamImplicitWidget(project);
        Disposer.register(this, paramImplicitWidget);
        TabInfo paramImplicitInfo = new TabInfo(paramImplicitWidget.container);
        paramImplicitInfo.setText("隐式参数");
        paramTabs.addTab("隐式参数", paramImplicitInfo.getComponent());

    }

    private void initResponsePanel(){
        responseBodyWidget = new RespBodyWidget(project);
        Disposer.register(this, responseBodyWidget);
        TabInfo resBodyInfo = new TabInfo(responseBodyWidget.container);
        resBodyInfo.setText("返回值");
        respTabs.addTab("返回值", resBodyInfo.getComponent());
    }

    public void resetPanel(DubboMethod dubboMethod){
        if (dubboMethod != null){
            interfaceTextField.setText(dubboMethod.getInface());
            methodTextField.setText(dubboMethod.getName());
            versionTextField.setText(dubboMethod.getVersion() != null ? dubboMethod.getVersion() :  "");
            groupTextField.setText(dubboMethod.getGroup() != null ? dubboMethod.getGroup() :  "");

            List<Type> paramTypeList = dubboMethod.getParamTypes();
            Collection<String> paramTypes = paramTypeList.stream().map(Type::getTypeFullName).collect(Collectors.toList());

            Map<String, Object> map = new LinkedHashMap<>();
            map.put("paramType", paramTypes);
            map.put("param", Collections.EMPTY_LIST);
            paramWidget.setParamBody(map);
        } else {
            interfaceTextField.setText("");
            methodTextField.setText("");
            versionTextField.setText("");
            groupTextField.setText("");

            Map<String, Object> map = new LinkedHashMap<>();
            map.put("paramType", Collections.EMPTY_LIST);
            map.put("param", Collections.EMPTY_LIST);
            paramWidget.setParamBody(map);
        }
    }

    public void resetPanel(DubboFavorite favorite){
    }
    public void resetPanel(DubboHistory history){
    }



    public JPanel getMyMainPanel() {
        return myMainPanel;
    }

    private void createUIComponents() {
    }

    @Override
    public void dispose() {

    }
}
